using System;
using System.IO;
using System.Net;
using System.Text;
using System.Xml;
using Tumblr.API.PostType;

namespace Tumblr.API
{
    public class TumblrApi
{
        private readonly XmlDocument _outputXml;

        public TumblrApi(Account account)
        {
            Account = account;
            _outputXml = new XmlDocument();
        }

        public Account Account
        {
            get; set; 
        }

        public delegate void TumblrEventHandler(object sender, TumblrEventArgs te);
        public event TumblrEventHandler TumblrEvent;
        public void ActivateTumblrEvent(Status status) 
        {
            var tumblrArgs = new TumblrEventArgs(status);
            if (TumblrEvent != null)
            {
                TumblrEvent(this, tumblrArgs);
            }
        }

        // This is important!
        // The only rule when using this source so that derivative works can be identified
        // You MUST append/prepend/include within the generator tag the phrase:
        // "WinTumblr" or "with WinTumblr" or "WinTumblr modified by" or "based on WinTumblr"
        private const string Generator = "WinTumblr v1.5.2 http://www.feedfeast.com/wintumblr";

        private Status PostIt(byte[] data, ApiType apiType)
        {
            var apiUrl = String.Empty;
            switch (apiType)
            {
                case ApiType.Write:
                    apiUrl = "http://www.tumblr.com/api/write";
                    break;
                case ApiType.Authentication:
                    apiUrl = "http://www.tumblr.com/api/authenticate";
                    break;
            }

            // Generic Post to Tumblr method            
            HttpWebResponse response = null;
            Stream newStream = null;
            var status = new Status();
            var myRequest = (HttpWebRequest)WebRequest.Create(apiUrl);
            WebProxy myProxy;
            if ((Proxy.UseProxy != null) && ((bool)Proxy.UseProxy))
            {
                try
                {
                    myProxy = new WebProxy();
                    var newUri = new Uri(Proxy.ServerUrl + ":" + Proxy.ServerPort);
                    myProxy.Address = newUri;
                    myProxy.Credentials = !string.IsNullOrEmpty(Proxy.Domain) ? new NetworkCredential(Proxy.UserName, Proxy.Password, Proxy.Domain) : new NetworkCredential(Proxy.UserName, Proxy.Password);
                    myRequest.Proxy = myProxy;
                }
                catch (Exception ex)
                {
                    status.Msg = Convert.ToString(ex.Message);
                }
            }

            myRequest.Credentials = CredentialCache.DefaultCredentials;
            myRequest.Method = "POST";
            myRequest.ContentType = "application/x-www-form-urlencoded; charset=utf-8";
            myRequest.ReadWriteTimeout = 600000;
            myRequest.Timeout = 600000;
            myRequest.ContentLength = data.Length;
            try
            {
                newStream = myRequest.GetRequestStream();
                newStream.WriteTimeout = 600000; 
                newStream.Write(data, 0, data.Length);
                response = (HttpWebResponse) myRequest.GetResponse();
                status.Code = Convert.ToInt32(response.StatusCode);
                status.Msg = response.StatusDescription;

                if (response.ContentType == "text/xml" )
                    _outputXml.Load(response.GetResponseStream());
            }
            catch (ProtocolViolationException ex)
            {
                status.Msg = Convert.ToString(ex.Message);
            }
            catch (NotSupportedException ex)
            {
                status.Msg = Convert.ToString(ex.Message);
            }
            catch (WebException ex)
            {
                status.Msg = Convert.ToString(ex.Message);
            }
            catch (Exception e)
            {
                status.Msg = Convert.ToString(e.Message);
            }
            finally
            {
                if (response != null)
                {
                    response.Close();
                }
                if (newStream != null)
                {
                    newStream.Close();
                }
                ActivateTumblrEvent(status);
            }
            return status;
        }

        public delegate Status ApostText(Text text);

        public Status PostText(Text text)
        {
            var enc = Encoding.UTF8.GetEncoder();
            var postData = "email=" + Account.Email;
            postData += "&password=" + Account.Password;
            postData += "&type=regular";
            if (string.IsNullOrEmpty(text.Title) && string.IsNullOrEmpty(text.Body))
            {
                throw new ArgumentNullException();
            }
            if (!string.IsNullOrEmpty(text.Title))
            {
                postData += "&title=" + text.Title;
            }
            if (!string.IsNullOrEmpty(text.Body))
            {
                postData += "&body=" + text.Body;
            }

            postData += "&generator=" + Generator;
            if (Account.StrDateOfPost.Length > 0)
            {
                postData += "&date=" + Account.StrDateOfPost;
            }
            if (Account.IsPrivate)
            {
                postData += "&private=1";
            }
            if (text.Tags.Length > 0)
            {
                postData += "&tags=" + text.Tags;
            }

            var chr = postData.ToCharArray();
            var byteCount = enc.GetByteCount(chr, 0, chr.Length, true);
            var data = new Byte[byteCount];
            enc.GetBytes(chr, 0, chr.Length, data, 0, true);
            return PostIt(data, ApiType.Write);
         }

        public delegate Status ApostPhoto(Photo photo);
        public Status PostPhoto(Photo photo)
        {
            var enc = Encoding.UTF8.GetEncoder();
            var postData = "email=" + Account.Email;
            postData += "&password=" + Account.Password;
            postData += "&type=photo";
            if (string.IsNullOrEmpty(photo.Source) && string.IsNullOrEmpty(photo.Data))
            {
                throw new ArgumentNullException();
            }
            if (!string.IsNullOrEmpty(photo.Source))
            {
                postData += "&source=" + photo.Source;
            }
            else // Don't bother sending a picture if the source is here
            {
                if (!string.IsNullOrEmpty(photo.Data))
                {
                    postData += "&data=" + photo.Data;
                }
            }
            if (!string.IsNullOrEmpty(photo.Caption))
            {
                postData += "&caption=" + photo.Caption;
            }
            if (!string.IsNullOrEmpty(photo.ClickThroughUrl))
            {
                postData += "&click-through-url=" + photo.ClickThroughUrl;
            }
            postData += "&generator=" + Generator;
            if (Account.StrDateOfPost.Length > 0)
            {
                postData += "&date=" + Account.StrDateOfPost;
            }
            if (Account.IsPrivate)
            {
                postData += "&private=1";
            }
            if (photo.Tags.Length > 0)
            {
                postData += "&tags=" + photo.Tags;
            }

            var chr = postData.ToCharArray();
            var byteCount = enc.GetByteCount(chr, 0, chr.Length, true);
            var data = new Byte[byteCount];
            enc.GetBytes(chr, 0, chr.Length, data, 0, true);
            return PostIt(data, ApiType.Write);
        }

        public delegate Status ApostQuote(Quote quote);
        public Status PostQuote(Quote quote)
        {
            var enc = Encoding.UTF8.GetEncoder();
            var postData = "email=" + Account.Email;
            postData += "&password=" + Account.Password;
            postData += "&type=quote";
            if (string.IsNullOrEmpty(quote.TheQuote))
            {
                throw new ArgumentNullException();
            }

            if ((quote.TheQuote != ""))
            {
                postData += "&quote=" + quote.TheQuote;
            }
            if (!string.IsNullOrEmpty(quote.Source))
            {
                postData += "&source=" + quote.Source;
            }
            postData += "&generator=" + Generator;
            if (Account.StrDateOfPost.Length > 0)
            {
                postData += "&date=" + Account.StrDateOfPost;
            }
            if (Account.IsPrivate)
            {
                postData += "&private=1";
            }
            if (quote.Tags.Length > 0)
            {
                postData += "&tags=" + quote.Tags;
            }

            var chr = postData.ToCharArray();
            var byteCount = enc.GetByteCount(chr, 0, chr.Length, true);
            var data = new Byte[byteCount];
            enc.GetBytes(chr, 0, chr.Length, data, 0, true);
            return PostIt(data, ApiType.Write);
         }

        public delegate Status ApostLink(Link link);
        public Status PostLink(Link link)
         {
             Encoder enc = Encoding.UTF8.GetEncoder();
             string postData = "email=" + Account.Email;
             postData += "&password=" + Account.Password;
             postData += "&type=link";
             if (string.IsNullOrEmpty(link.Url))
             {
                 throw new ArgumentNullException();
             }
            if (!string.IsNullOrEmpty(link.Name))
            {
                postData += "&name=" + link.Name;
            }
            if ((link.Url != ""))
            {
                postData += "&url=" + link.Url;
            }
            if (!string.IsNullOrEmpty(link.Description))
            {
                postData += "&description=" + link.Description;
            }
            postData += "&generator=" + Generator;
            if (Account.StrDateOfPost.Length > 0)
             {
                 postData += "&date=" + Account.StrDateOfPost;
             }
            if (Account.IsPrivate)
             {
                 postData += "&private=1";
             }
             if (link.Tags.Length > 0)
             {
                 postData += "&tags=" + link.Tags;
             }

             var chr = postData.ToCharArray();
             var byteCount = enc.GetByteCount(chr, 0, chr.Length, true);
             var data = new Byte[byteCount];
             enc.GetBytes(chr, 0, chr.Length, data, 0, true);
             return PostIt(data, ApiType.Write);
         }

        public delegate Status ApostChat(Chat chat);

        public Status PostChat(Chat chat)
         {
             Encoder enc = Encoding.UTF8.GetEncoder();
             string postData = "email=" + Account.Email;
             postData += "&password=" + Account.Password;
             postData += "&type=conversation";
             if (string.IsNullOrEmpty(chat.TheChat))
             {
                 throw new ArgumentNullException();
             }
            if (!string.IsNullOrEmpty(chat.Title))
            {
                postData += "&title=" + chat.Title;
            }
            if ((chat.TheChat != ""))
            {
                postData += "&conversation=" + chat.TheChat;
            }
            postData += "&generator=" + Generator;
            if (Account.StrDateOfPost.Length > 0)
             {
                 postData += "&date=" + Account.StrDateOfPost;
             }
            if (Account.IsPrivate)
             {
                 postData += "&private=1";
             }
             if (chat.Tags.Length > 0)
             {
                 postData += "&tags=" + chat.Tags;
             }

             var chr = postData.ToCharArray();
             var byteCount = enc.GetByteCount(chr, 0, chr.Length, true);
             var data = new Byte[byteCount];
             enc.GetBytes(chr, 0, chr.Length, data, 0, true);
             return PostIt(data, ApiType.Write);
         }

        public delegate Status ApostVideo(Video video);
        public Status PostVideo(Video video)
         {
             Encoder enc = Encoding.UTF8.GetEncoder();
             string postData = "email=" + Account.Email;
             postData += "&password=" + Account.Password;
             postData += "&type=video";
             if (string.IsNullOrEmpty(video.Embed))
             {
                 throw new ArgumentNullException();
             }
            if ((video.Embed != ""))
            {
                postData += "&embed=" + video.Embed;
            }
            if (!string.IsNullOrEmpty(video.Caption))
            {
                postData += "&caption=" + video.Caption;
            }
            postData += "&generator=" + Generator;
            if (Account.StrDateOfPost.Length > 0)
             {
                 postData += "&date=" + Account.StrDateOfPost;
             }
            if (Account.IsPrivate)
             {
                 postData += "&private=1";
             }
             if (video.Tags.Length > 0)
             {
                 postData += "&tags=" + video.Tags;
             }

             var chr = postData.ToCharArray();
             var byteCount = enc.GetByteCount(chr, 0, chr.Length, true);
             var data = new Byte[byteCount];
             enc.GetBytes(chr, 0, chr.Length, data, 0, true);
             return PostIt(data, ApiType.Write);
         }

        public delegate Status AAuthenticate(Account account);
        public Status Authenticate(Account account)
         {
             var enc = Encoding.UTF8.GetEncoder();
             var postData = "email=" + account.Email;
             postData += "&password=" + account.Password;
             postData += "&action=authenticate";
             postData += "&generator=" + Generator;

             var chr = postData.ToCharArray();
             var byteCount = enc.GetByteCount(chr, 0, chr.Length, true);
             var data = new Byte[byteCount];
             enc.GetBytes(chr, 0, chr.Length, data, 0, true);
             return PostIt(data, ApiType.Write);
         }

        public delegate Status ACheckVimeo(Account account);
        public Status CheckVimeo(Account account)
         {
             var enc = Encoding.UTF8.GetEncoder();
             var postData = "email=" + account.Email;
             postData += "&password=" + account.Password;
             postData += "&action=check-vimeo";
             postData += "&generator=" + Generator;

             var chr = postData.ToCharArray();
             var byteCount = enc.GetByteCount(chr, 0, chr.Length, true);
             var data = new Byte[byteCount];
             enc.GetBytes(chr, 0, chr.Length, data, 0, true);
             return PostIt(data, ApiType.Write);
         }

        public delegate Status ACheckAudio(Account account);
        public Status CheckAudio(Account account)
         {
             var enc = Encoding.UTF8.GetEncoder();
             var postData = "email=" + account.Email;
             postData += "&password=" + account.Password;
             postData += "&action=check-audio";
             postData += "&generator=" + Generator;

             var chr = postData.ToCharArray();
             var byteCount = enc.GetByteCount(chr, 0, chr.Length, true);
             var data = new Byte[byteCount];
             enc.GetBytes(chr, 0, chr.Length, data, 0, true);
             return PostIt(data, ApiType.Write);
         }

        public AccountInfo GetAccountInfo()
        {
            var enc = Encoding.UTF8.GetEncoder();
            var postData = "email=" + Account.Email;
            postData += "&password=" + Account.Password;

            var chr = postData.ToCharArray();
            var byteCount = enc.GetByteCount(chr, 0, chr.Length, true);
            var data = new Byte[byteCount];
            enc.GetBytes(chr, 0, chr.Length, data, 0, true);
            var status = PostIt(data, ApiType.Authentication);

            return status.Code == 200 ? AccountInfo.Deserialize(_outputXml) : null;
        }
}
}